package cn.backpackerxl.easyJDBC.util;

import cn.backpackerxl.easyJDBC.annotation.DFormat;
import cn.backpackerxl.easyJDBC.annotation.Ignore;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.stream.Collectors;

import static cn.backpackerxl.easyJDBC.util.StringUtil.*;

/**
 * 对象 JSON 化
 *
 * @author: backpackerxl
 * @create: 2022/1/11
 * @filename: JSON
 **/
public final class JSON {

    private static Object entity;

    private static final JSON json = new JSON();

    private static URL url;

    /**
     * 设置转化对象
     *
     * @param clazz 要转化的数据类型
     * @return JSON
     */
    public static JSON setValid(Class<?> clazz) {
        try {
            entity = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return json;
    }

    private JSON() {

    }

    /**
     * 加载json
     *
     * @param name 文件名
     * @return JSON
     */
    public static JSON load(String name) {
        url = Thread.currentThread().getContextClassLoader().getResource(name);
        return json;
    }

    /**
     * 加载json
     *
     * @param name  文件名
     * @param clazz 类型
     * @return JSON
     */
    public static JSON load(String name, Class<?> clazz) {
        url = Thread.currentThread().getContextClassLoader().getResource(name);
        return setValid(clazz);
    }

    /**
     * 加载json
     *
     * @param url 文件路径
     * @return JSON
     */
    public static JSON load(URL url) {
        JSON.url = url;
        return json;
    }

    /**
     * 加载json
     *
     * @param url   文件路径
     * @param clazz 类型
     * @return JSON
     */
    public static JSON load(URL url, Class<?> clazz) {
        JSON.url = url;
        return setValid(clazz);
    }

    /**
     * 保存json
     *
     * @param path 保存路径
     * @param json 内容
     * @throws IOException
     */
    public static void save(Path path, String json) throws IOException {
        if (Files.exists(path)) {
            Files.delete(path);
        }
        Files.createFile(path);
        Files.write(path, json.getBytes(), StandardOpenOption.APPEND);
    }

    /**
     * 保存json
     *
     * @param path 保存路径
     * @param e    内容
     * @throws IOException
     */
    public static <E> void save(Path path, E e) throws IOException {
        save(path, stringify(e));
    }

    /**
     * 保存json
     *
     * @param path 保存路径
     * @param e    内容
     * @throws IOException
     */
    public static <E> void save(Path path, List<E> e) throws IOException {
        save(path, stringify(e));
    }

    /**
     * 保存json
     *
     * @param path 保存路径
     * @param e    内容
     * @throws IOException
     */
    public static <E> void save(Path path, Map<String, E> e) throws IOException {
        save(path, stringify(e));
    }

    /**
     * 保存json
     *
     * @param path 保存路径
     * @param e    内容
     * @throws IOException
     */
    public static <T> void save(Path path, T[] e) throws IOException {
        save(path, stringify(e));
    }


    /**
     * 单个对象转化为JSON字符串
     *
     * @param entity 实体类型的转化对象
     * @return JSON字符串
     */
    public static <E> String stringify(E entity) {
        Class<?> clazz = entity.getClass();
        StringBuilder res = new StringBuilder();
        if (BasicUtil.isMyObj(clazz.getTypeName())) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                String typeName = field.getType().getName();
                Object value = ObjectIsNull(field, entity);
                if (!field.isAnnotationPresent(Ignore.class)) {
                    if (Typeof.STRING.value().equals(typeName) || typeName.equals(Typeof.CHAR.value())) {
                        if (value == null) {
                            res.append("\"").append(removeHump(field.getName())).append("\":").append((Object) null).append(",");
                        } else {
                            res.append("\"").append(removeHump(field.getName())).append("\":\"").append(value).append("\",");
                        }
                    } else {
                        if (Typeof.LIST.value().equals(typeName)) {
                            res.append("\"").append(removeHump(field.getName())).append("\":").append(value == null ? "[]" : stringify((List<E>) value)).append(",");
                        } else if (Typeof.MAP.value().equals(typeName)) {
                            res.append("\"").append(removeHump(field.getName())).append("\":").append(value == null ? "[]" : stringify((Map<String, E>) value)).append(",");
                        } else if (Typeof.DATE.value().equals(typeName)) {
                            if (value == null) {
                                res.append("\"").append(removeHump(field.getName())).append("\":").append("null").append(",");
                            } else {
                                Object o = field.isAnnotationPresent(DFormat.class) ? BasicUtil.formatTime(value, field.getDeclaredAnnotation(DFormat.class).value().get()) : BasicUtil.formatTime(value, "#");
                                if (o instanceof Long) {
                                    res.append("\"").append(removeHump(field.getName())).append("\":").append(o).append(",");
                                } else {
                                    res.append("\"").append(removeHump(field.getName())).append("\":\"").append(o).append("\",");
                                }
                            }
                        } else if (BasicUtil.isMyObj(typeName)) {
                            if (BasicUtil.isArray(value)) {
                                res.append("\"").append(removeHump(field.getName())).append("\":").append(compileArray(value)).append(",");
                            } else {
                                res.append("\"").append(removeHump(field.getName())).append("\":").append(value == null ? null : stringify(value)).append(",");
                            }
                        } else {
                            if ((Typeof.$LONG.value().equals(typeName) || Typeof.LONG.value().equals(typeName)) && String.valueOf(value).length() >= 16) {
                                res.append("\"").append(removeHump(field.getName())).append("\":\"").append(value).append("\",");
                            } else {
                                res.append("\"").append(removeHump(field.getName())).append("\":").append(value).append(",");
                            }
                        }
                    }
                }
            }
            return res.length() == 0 ? "{" + res + "}" : "{" + tail(res.toString()) + "}";
        } else {
            if (Typeof.STRING.value().equals(clazz.getTypeName()) || clazz.getTypeName().equals(Typeof.CHAR.value())) {
                res.append(stringify(String.valueOf(entity)));
            } else {
                res.append(entity);
            }
            return res.toString();
        }
    }

    /**
     * 判断属性值是否为空
     *
     * @param field  属性
     * @param entity 实体
     * @param <E>    实体泛型
     * @return 属性值
     */
    private static <E> Object ObjectIsNull(Field field, E entity) {
        try {
            if (entity != null) {
                return field.get(entity);
            } else {
                return null;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 字符串转化为json
     *
     * @param s
     * @return
     */
    public static String stringify(String s) {
        return String.format("%s%s%s", "\"", s, "\"");
    }

    /**
     * 数组转化为json
     *
     * @param object
     * @param <T>
     * @return
     */
    public static <T> String stringify(T[] object) {
        if (object.length == 0) {
            return "[]";
        }
        return compileArray(object);
    }


    /**
     * List数据的转化为JSON字符串
     *
     * @param data List<E>类型的数据
     * @return JSON字符串
     */
    public static <E> String stringify(List<E> data) {
        if (data.size() == 0) {
            return "[]";
        }
        StringBuilder res = new StringBuilder();
        for (E entity : data) {
            res.append(stringify(entity)).append(",");
        }
        return res.length() == 0 ? "[" + res + "]" : "[" + tail(res.toString()) + "]";
    }

    /**
     * Map数据的转化为JSON字符串
     *
     * @param data Map<k, v>类型的数据
     * @return JSON字符串
     */
    public static <E> String stringify(Map<String, E> data) {
        if (data.size() == 0) {
            return "[]";
        }
        StringBuilder res = new StringBuilder();
        for (String k : data.keySet()) {
            res.append("{\"").append(k).append("\":").append(stringify(data.get(k))).append("},");
        }
        return res.length() == 0 ? "[" + res + "]" : "[" + tail(res.toString()) + "]";
    }

    /**
     * 将JSON数组转化为实体对象
     *
     * @param json JSON字符串
     * @return 转化后的对象
     */
    public <E> List<E> parseValueAll(String json) {
        List<E> res = new ArrayList<>();
        if (json.length() > 0) {
            List<JSONObject> arrays = new Parse(json).parse().getArrays();
            for (JSONObject j : arrays) {
                Map<String, JSONObject> value = j.getObjValue();
                if (value.size() == 0) {
                    res.add(null);
                } else {
                    res.add(this.getEntity(value, entity.getClass()));
                }
            }
        }
        return res;
    }

    /**
     * 将JSON数组转化为实体对象
     *
     * @return 转化后的对象
     */
    public <E> List<E> parseValueAll() throws IOException {
        String json = StringUtil.getStreamString(url);
        return parseValueAll(json);
    }

    /**
     * 单个json对象转化为实体对象
     *
     * @param json JSON字符串
     * @return 转化后的对象
     */
    public <E> E parseValue(String json) {
        if (json.length() > 0) {
            Map<String, JSONObject> object = new Parse(json).parse().getObjValue();
            return this.getEntity(object, entity.getClass());
        } else {
            return null;
        }
    }

    /**
     * 单个json对象转化为实体对象
     *
     * @return 转化后的对象
     */
    public <E> E parseValue() throws IOException {
        String json = StringUtil.getStreamString(url);
        return parseValue(json);
    }

    /**
     * json对象转化为JSONObject
     *
     * @param json JSON字符串
     * @return <b>JSONObject</b> 对象
     */
    public static JSONObject parse(String json) {
        return json.length() > 0 ? new Parse(json).parse() : null;
    }

    /**
     * json对象转化为 JSONObject
     *
     * @return <b>JSONObject</b> 对象
     */
    public JSONObject parse() throws IOException {
        String s = getStreamString(url);
        return parse(s);
    }

    /**
     * 获得一个实体对象
     *
     * @param rs 结果
     * @return 转化后的对象
     */
    private <E, T> E getEntity(Map<String, JSONObject> rs, Class<?> clazz) {
        E obj = null;

        try {
            obj = (E) clazz.newInstance(); // 需要重新生成一个对象, 避免后面的值覆盖前面的值
            Field[] fields = clazz.getDeclaredFields();
            JSONRuleEngine ruleEngine = new JSONRuleEngine();
            for (Field field : fields) {
                field.setAccessible(true);
                String typeName = field.getType().getTypeName();
                if (!rs.containsKey(StringUtil.removeHump(field.getName())) && !BasicUtil.isType(typeName)) {
                    field.set(obj, null);
                } else {
                    JSONObject object = rs.get(removeHump(field.getName()));
                    if (!Objects.isNull(object)) {
                        if (BasicUtil.isMyObj(typeName) && !BasicUtil.isArray(field)) {
                            field.set(obj, this.getEntity(object.getObjValue(), field.getType()));
                        } else if (BasicUtil.isBasicArray(field) && !BasicUtil.isType(typeName.replace("[]", ""))) {
                            Class<?> type = field.getType().getComponentType();
                            List<Object> collects = object.getArrays().stream().map(jsonObject -> this.getEntity(jsonObject.getObjValue(), type)).collect(Collectors.toList());
                            T[] res = collects.toArray((T[]) Array.newInstance(type, collects.size()));
                            field.set(obj, res);
                        } else if (MatchType.get(typeName) == Typeof.MAP) {
                            ParameterizedType genericType = (ParameterizedType) field.getGenericType();
                            Type[] arguments = genericType.getActualTypeArguments();
                            if (MatchType.get(arguments[0].getTypeName()) == Typeof.STRING) {
                                List<Map<String, JSONObject>> collect = object.getArrays().stream().map(JSONObject::getObjValue).collect(Collectors.toList());
                                Map<String, T> tMap = new HashMap<>();
                                if (BasicUtil.isMyObj(arguments[1].getTypeName())) {
                                    for (Map<String, JSONObject> objectMap : collect) {
                                        for (String s : objectMap.keySet()) {
                                            tMap.put(s, this.getEntity(objectMap.get(s).getObjValue(), (Class<?>) arguments[1]));
                                        }
                                    }
                                    field.set(obj, tMap);
                                } else {
                                    JSONExpression expression = new JSONExpression(genericType, obj, object, field);
                                    ruleEngine.process(expression);
                                }
                            } else {
                                throw new RuntimeException("parse Map error, the Map key must is String.");
                            }
                        } else if (MatchType.get(typeName) == Typeof.LIST) {
                            ParameterizedType genericType = (ParameterizedType) field.getGenericType();
                            Type[] arguments = genericType.getActualTypeArguments();
                            List<T> list = new ArrayList<>();
                            List<JSONObject> arrays = object.getArrays();
                            if (BasicUtil.isMyObj(arguments[0].getTypeName())) {
                                for (JSONObject jsonObject : arrays) {
                                    list.add(this.getEntity(jsonObject.getObjValue(), (Class<?>) arguments[0]));
                                }
                                field.set(obj, list);
                            } else {
                                JSONExpression expression = new JSONExpression(genericType, obj, object, field);
                                ruleEngine.process(expression);
                            }
                        } else {
                            JSONExpression expression = new JSONExpression(typeName, obj, object, field);
                            ruleEngine.process(expression);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    /**
     * 处理数组的情况
     *
     * @param o 将数组值转化为JSON值
     * @return JSON字符串值
     */
    private static String compileArray(Object o) {
        if (o instanceof int[]) {
            return Arrays.toString((int[]) o);
        } else if (o instanceof String[]) {
            return buildArray((String[]) o);
        } else if (o instanceof char[]) {
            return buildArray((char[]) o);
        } else if (o instanceof long[]) {
            return Arrays.toString((long[]) o);
        } else if (o instanceof byte[]) {
            return Arrays.toString((byte[]) o);
        } else if (o instanceof short[]) {
            return Arrays.toString((short[]) o);
        } else if (o instanceof boolean[]) {
            return Arrays.toString((boolean[]) o);
        } else if (o instanceof double[]) {
            return Arrays.toString((double[]) o);
        } else if (o instanceof float[]) {
            return Arrays.toString((float[]) o);
        } else if (o instanceof Integer[]) {
            return Arrays.toString((Integer[]) o);
        } else if (o instanceof Long[]) {
            return Arrays.toString((Long[]) o);
        } else if (o instanceof Byte[]) {
            return Arrays.toString((Byte[]) o);
        } else if (o instanceof Boolean[]) {
            return Arrays.toString((Boolean[]) o);
        } else if (o instanceof Short[]) {
            return Arrays.toString((Short[]) o);
        } else if (o instanceof Double[]) {
            return Arrays.toString((Double[]) o);
        } else if (o instanceof Float[]) {
            return Arrays.toString((Float[]) o);
        } else if (o instanceof Object[]) {
            return buildArray((Object[]) o);
        }
        return "";
    }

    /**
     * 处理String[]
     *
     * @param obs String[]类型
     * @return JSON字符串
     */
    private static String buildArray(String[] obs) {
        StringBuilder out = new StringBuilder();
        for (String obj : obs) {
            out.append("\"").append(obj).append("\"").append(",");
        }
        return tail(out.toString()) != null ? "[" + tail(out.toString()) + "]" : "[]";
    }

    /**
     * 处理char[]
     *
     * @param obs char[]类型
     * @return JSON字符串
     */
    private static String buildArray(char[] obs) {
        StringBuilder out = new StringBuilder();
        for (char obj : obs) {
            out.append("\"").append(obj).append("\"").append(",");
        }
        return tail(out.toString()) != null ? "[" + tail(out.toString()) + "]" : "[]";
    }

    /**
     * 处理Object[]
     *
     * @param obs Object[]类型
     * @return JSON字符串
     */
    private static String buildArray(Object[] obs) {
        StringBuilder out = new StringBuilder();
        for (Object obj : obs) {
            out.append(stringify(obj)).append(",");
        }
        return tail(out.toString()) != null ? "[" + tail(out.toString()) + "]" : "[]";
    }
}
